package com.browseengine.bobo.facets.data;

import it.unimi.dsi.fastutil.longs.LongArrayList;

import java.util.Iterator;
import java.util.List;

public class TermFixedLengthLongArrayList extends TermValueList<long[]> {
  protected long[] _elements = null;
  protected int width;

  private final long[] sanity;
  private boolean withDummy = true;

  public TermFixedLengthLongArrayList(int width) {
    super();
    this.width = width;
    sanity = new long[width];
    sanity[width - 1] = -1;
  }

  public TermFixedLengthLongArrayList(int width, int capacity) {
    super(capacity * width);
    this.width = width;
    sanity = new long[width];
    sanity[width - 1] = -1;
  }

  protected long[] parse(String s) {
    long[] r = new long[width];

    if (s == null || s.length() == 0) return r;

    String[] a = s.split(",");
    if (a.length != width) throw new RuntimeException(s + " is not a " + width
        + " fixed width long.");

    for (int i = 0; i < width; ++i) {
      r[i] = Long.parseLong(a[i]);
      // if (r[i] < 0)
      // throw new RuntimeException("We only support non-negative numbers: " + s);
    }

    return r;
  }

  @Override
  public boolean add(String o) {
    int i = 0;
    long cmp = 0;

    if (_innerList.size() == 0 && o != null) // the first value added is not null
    withDummy = false;

    long[] item = parse(o);

    for (i = 0; i < width; ++i) {
      cmp = item[i] - sanity[i];
      if (cmp != 0) break;
    }

    // if (cmp<=0)
    // throw new
    // RuntimeException("Values need to be added in ascending order and we only support non-negative numbers: "
    // + o);

    for (i = 0; i < width; ++i) {
      if (!((LongArrayList) _innerList).add(item[i])) {
        if (i > 0) {
          ((LongArrayList) _innerList).removeElements(_innerList.size() - i, _innerList.size() - 1);
        }
        return false;
      }
    }

    if (_innerList.size() > width || !withDummy) for (i = 0; i < width; ++i)
      sanity[i] = item[i];

    return true;
  }

  @Override
  protected List<?> buildPrimitiveList(int capacity) {
    _type = long[].class;
    return capacity > 0 ? new LongArrayList(capacity) : new LongArrayList();
  }

  @Override
  public void clear() {
    super.clear();
  }

  @Override
  public String get(int index) {
    index = index * width;
    StringBuilder sb = new StringBuilder();
    sb.append(_elements[index]);

    int left = width;
    ++index;
    while (left > 1) {
      sb.append(',');
      sb.append(_elements[index]);
      --left;
      ++index;
    }

    return sb.toString();
  }

  @Override
  public long[] getRawValue(int index) {
    long[] val = new long[width];

    index = index * width;
    for (int i = 0; i < width; ++i) {
      val[i] = _elements[index + i];
    }

    return val;
  }

  @Override
  public Iterator<String> iterator() {
    final Iterator<?> iter = _innerList.iterator();

    return new Iterator<String>() {
      @Override
      public boolean hasNext() {
        return iter.hasNext();
      }

      @Override
      public String next() {
        long[] val = new long[width];
        for (int i = 0; i < width; ++i) {
          val[i] = (Long) iter.next();
        }
        return format(val);
      }

      @Override
      public void remove() {
        for (int i = 0; i < width; ++i)
          iter.remove();
      }
    };
  }

  @Override
  public int size() {
    return _innerList.size() / width;
  }

  @Override
  public Object[] toArray() {
    Object[] retArray = new Object[size()];
    for (int i = 0; i < retArray.length; ++i) {
      retArray[i] = get(i);
    }
    return retArray;
  }

  public long[][] toArray(long[][] a) {
    long[][] retArray = new long[size()][];
    for (int i = 0; i < retArray.length; ++i) {
      retArray[i] = getRawValue(i);
    }
    return retArray;
  }

  @Override
  public String format(Object o) {
    if (o == null) return null;

    if (o instanceof String) o = parse((String) o);

    long[] val = (long[]) o;

    if (val.length == 0) return null;

    StringBuilder sb = new StringBuilder();
    sb.append(val[0]);

    for (int i = 1; i < val.length; ++i) {
      sb.append(',');
      sb.append(val[i]);
    }
    return sb.toString();
  }

  public long[] getPrimitiveValue(int index) {
    index = index * width;
    long[] r = new long[width];

    if (index < _elements.length) {
      for (int i = 0; i < width; ++i, ++index)
        r[i] = _elements[index];
    } else {
      r[width - 1] = -1;
    }
    return r;
  }

  protected int binarySearch(long[] key) {
    return binarySearch(key, 0, _elements.length / width - 1);
  }

  protected int binarySearch(long[] key, int low, int high) {
    int mid = 0;
    long cmp = -1;
    int index, i;

    while (low <= high) {
      mid = (low + high) / 2;
      index = mid * width;
      for (i = 0; i < width; ++i, ++index) {
        cmp = key[i] - _elements[index];

        if (cmp != 0) break;
      }
      if (cmp > 0) low = mid + 1;
      else if (cmp < 0) high = mid - 1;
      else return mid;
    }
    return -(mid + 1);
  }

  @Override
  public int indexOf(Object o) {
    if (withDummy) {
      if (o instanceof String) o = parse((String) o);
      return binarySearch((long[]) o, 1, _elements.length / width - 1);
    } else {
      if (o instanceof String) o = parse((String) o);
      return binarySearch((long[]) o);
    }
  }

  public int indexOf(long[] val) {
    if (withDummy) {
      return binarySearch(val, 1, _elements.length / width - 1);
    } else return binarySearch(val);
  }

  @Override
  public int indexOfWithType(long[] val) {
    if (withDummy) {
      return binarySearch(val, 1, _elements.length / width - 1);
    } else return binarySearch(val);
  }

  @Override
  public Comparable<?> getComparableValue(int index) {
    return get(index);
  }

  @Override
  public void seal() {
    ((LongArrayList) _innerList).trim();
    _elements = ((LongArrayList) _innerList).elements();
  }

  public boolean contains(long[] val) {
    if (withDummy) {
      return binarySearch(val, 1, _elements.length / width - 1) >= 0;
    } else return binarySearch(val) >= 0;
  }

  @Override
  public boolean containsWithType(long[] val) {
    if (withDummy) {
      return binarySearch(val, 1, _elements.length / width - 1) >= 0;
    } else return binarySearch(val) >= 0;
  }
}
